﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Threading;
using System.Net;
using System.Runtime.InteropServices;
using System.IO;

namespace Engine.NetClasses
{
    public class BaseServer
    {
        private ServerOptions m_serverOptions;
        private ServerState m_serverState;
        private ServerLogs m_serverLog;
        private ServerInput m_serverInput;
        private bool m_serverOnline;
        private bool m_nowRestart;
        private Thread m_mainLoop;
        /// <summary>
        /// The socket used to listen for incoming connection requests
        /// </summary>
        private Socket m_listenSocket;
        /// <summary>
        /// Connected offer Servers
        /// </summary>
        private List<Socket> m_conectToServers;
        private BufferManager m_bufferManager;
        private SocketAsyncEventArgsPool m_readWritePool;
        private List<IAsyncClient> Clients;
        private Func<IAsyncClient> m_AsyncClient;
        private Semaphore m_maxNumberAcceptedClients;
        public Func<IAsyncClient> AsyncClientBilder
        {
            get
            {
                return m_AsyncClient;
            }
            set
            {
                m_AsyncClient = value;
            }
        }
        public BufferManager BuferManager
        {
            get { return m_bufferManager; }
            set { m_bufferManager = value; }
        }
        public Socket[] ServerSocketList
        {
            get { return m_conectToServers.ToArray(); }
        }
        public ServerInput ServerConsole
        {
            get { return m_serverInput; }
            set { m_serverInput = value; }
        }
        public ServerLogs ServerLog
        {
            get { return m_serverLog; }
            set { m_serverLog = value; }
        }
        public ServerState ServerState
        {
            get { return m_serverState; }
            set { m_serverState = value; }
        }
        public ServerOptions ServerOptions
        {
            get { return m_serverOptions; }
            set { m_serverOptions = value; }
        }
        public List<Socket> _servers;
        public List<IAsyncClient> ConnectedClients { get { return Clients; } }
        private List<string> m_serverComand;

        public bool ServerWork
        {
            get
            {
                if (!m_serverOnline && !m_nowRestart)
                {
                    return false;
                }
                else
                {
                    return true;
                }
            }
        }
        public string[] Commands
        {
            get
            {
                string[] tmp;
                lock (m_serverComand)
                {
                    tmp = m_serverComand.ToArray();
                    m_serverComand.Clear();
                }
                return tmp;
            }
        }
        public string Command
        {
            set
            {
                lock (m_serverComand)
                {
                    m_serverComand.Add(value);
                }
            }
        }


        /// <summary>
        /// Create Base Server and deffauld config
        /// </summary>
        public BaseServer()
            : this(ServerOptions._ServerOptionFile)
        {

        }
        /// <summary>
        /// Create Base Server
        /// </summary>
        /// <param name="Name">File name server config</param>
        public BaseServer(string Name)
        {


            NetPacket.NetPacketBild();
            m_serverOnline = false;
            m_serverOptions = ServerOptions.Load(Name);
            m_serverState = new ServerState(m_serverOptions.MaxConnection);
            m_serverLog = new ServerLogs(m_serverOptions);
            m_serverInput = new ServerInput(m_serverLog, m_serverOptions, m_serverState, this);
            m_AsyncClient = new Func<IAsyncClient>(baseAsyncClient);
            Clients = new List<IAsyncClient>();
            m_serverComand = new List<string>(0);
            m_serverState.Server = this;
            if (m_serverOptions.ServerList!=null)
            {
                
            
            if (m_serverOptions.ServerList.Length>0)
            {
                _servers = new List<Socket>();
                for (int i = 0; i < m_serverOptions.ServerList.Length; i++)
                {
                    IPAddress ip=IPAddress.Parse(m_serverOptions.ServerList[i].IpServer);

                    _servers.Add(new Socket(ip.AddressFamily,SocketType.Stream,ProtocolType.Tcp));
                }
            }
            }
        }
        private IAsyncClient baseAsyncClient()
        {

            return new AsyncClient();
        }
        /// <summary>
        /// Init server parametr
        /// </summary>
        public void Init()
        {

            m_bufferManager = new BufferManager(m_serverOptions.ReciveBufferSize * m_serverOptions.MaxConnection * 2,
                  m_serverOptions.ReciveBufferSize);

            m_readWritePool = new SocketAsyncEventArgsPool(m_serverOptions.MaxConnection);
            m_maxNumberAcceptedClients = new Semaphore(m_serverOptions.MaxConnection, m_serverOptions.MaxConnection);
            // Allocates one large byte buffer which all I/O operations use a piece of.  This gaurds 
            // against memory fragmentation
            m_bufferManager.InitBuffer();

            // preallocate pool of SocketAsyncEventArgs objects
            SocketAsyncEventArgs readWriteEventArg;

            for (int i = 0; i < m_serverOptions.MaxConnection; i++)
            {
                //Pre-allocate a set of reusable SocketAsyncEventArgs
                readWriteEventArg = new SocketAsyncEventArgs();
                readWriteEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(IO_Completed);
                readWriteEventArg.UserToken = m_AsyncClient.Invoke();

                // assign a byte buffer from the buffer pool to the SocketAsyncEventArg object
                m_bufferManager.SetBuffer(readWriteEventArg);

                // add SocketAsyncEventArg to the pool
                m_readWritePool.Push(readWriteEventArg);
            }
        }

        // This method is called whenever a receive or send operation is completed on a socket 
        //
        // <param name="e">SocketAsyncEventArg associated with the completed receive operation</param>
        void IO_Completed(object sender, SocketAsyncEventArgs e)
        {
            // determine which type of operation just completed and call the associated handler
            switch (e.LastOperation)
            {
                case SocketAsyncOperation.Receive:
                    ProcessReceive(e);
                    break;
                case SocketAsyncOperation.Send:
                    ProcessSend(e);
                    break;
                case SocketAsyncOperation.Disconnect:
                    CloseClientSocket(e);
                    break;
                case SocketAsyncOperation.None:
                    break;



                default:
                    throw new ArgumentException("The last operation completed on the socket was not a receive or send");
            }

        }

        private void ProcessReceive(SocketAsyncEventArgs e)
        {
            // check if the remote host closed the connection
            IAsyncClient token = (IAsyncClient)e.UserToken;
            if (e.BytesTransferred > 0 && e.SocketError == SocketError.Success && !token.needDisconect)
            {

                token.Read(e);
              //  SerializationPacket(e,ref token);

                if (token.CountFromServer > 0)
                {
                    PacketsBilder[] tmp = token.GetFromServer();
                    for (int i = 0; i < tmp.Length; i++)
			{
                SendClient(token, tmp[i].getPacket());
			}
                   
                }
                
                    if (e.SocketError != SocketError.Disconnecting)
                    {
                        StartRecive(token, e);
                    }
                    else
                    {
                        CloseClientSocket(e);
                    }
                
            }
            else
            {
                CloseClientSocket(e);
            }
        }
        private void SerializationPacket(SocketAsyncEventArgs e,ref  IAsyncClient token)
        {
           
            
            if (e.Buffer[e.Offset + 2] != 0)
            {


                
                PacketsBilder[] packs= token.GetFromClient();
                for (int i = 0; i < packs.Length; i++)
                {
                    PacketsBilder pack = packs[i];
                    IPacket packet = pack.getStryct();
                    if (packet.PacketType == 5)
                    {
                        if (((NetPing)packet).Ping > m_serverOptions.PingTime / 2)
                        {
                            token.Ping = true;
                            token.PingTime = ((NetPing)packet).Ping;


                            SendClient(token, new PacketsBilder(NetPacket.GetPacket(0)).getPacket());
                            //token.Socket.Disconnect(true);
                            e.SocketError = SocketError.Disconnecting;

                        }
                        else
                        {
                            token.Ping = true;
                            token.PingTime = ((NetPing)packet).Ping;
                        }
                    }
                    else
                    {
                        if (!token.Ping)
                        {
                            e.SocketError = SocketError.Disconnecting;
                        }
                        else
                        {
                            token.SetFromClient(pack);
                        }
                    } 
                }
            }
            else
            {
                if (!token.Ping)
                {
                    e.SocketError = SocketError.Disconnecting;
                }
            }

        }


        private void ProcessSend(SocketAsyncEventArgs e)
        {
            if (e.SocketError == SocketError.Success)
            {

                 
            }
            else
            {
               // CloseClientSocket(e);
            }
        }
        private void CloseClientSocket(SocketAsyncEventArgs e)
        {
            IAsyncClient token = e.UserToken as IAsyncClient;

            // close the socket associated with the client
            try
            {
                token.Socket.Shutdown(SocketShutdown.Send);
            }
            // throws if client process has already closed
            catch (Exception) { }
            token.Socket.Close();
            token.onDataRead -= new DataRead(ReadData);
            // decrement the counter keeping track of the total number of clients connected to the server
            m_serverState.ClientDisconect();
            m_maxNumberAcceptedClients.Release();
            lock (Clients)
            {
                Clients.Remove(token);
            }
            m_serverLog.WriteLineLog(TypeLog.GlobalLog, "client ID='" + ((IAsyncClient)token).ID + "' disconnected");
            // Free the SocketAsyncEventArg so they can be reused by another client
            m_readWritePool.Push(e);
        }

        // Begins an operation to accept a connection request from the client 
        //
        // <param name="acceptEventArg">The context object to use when issuing 
        // the accept operation on the server's listening socket</param>
        public void StartAccept(SocketAsyncEventArgs acceptEventArg)
        {
            if (acceptEventArg == null)
            {
                acceptEventArg = new SocketAsyncEventArgs();
                acceptEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(AcceptEventArg_Completed);
            }
            else
            {
                // socket must be cleared since the context object is being reused
                acceptEventArg.AcceptSocket = null;
            }

            m_maxNumberAcceptedClients.WaitOne();
            bool willRaiseEvent = m_listenSocket.AcceptAsync(acceptEventArg);
            if (!willRaiseEvent)
            {
                ProcessAccept(acceptEventArg);
            }
        }

        // This method is the callback method associated with Socket.AcceptAsync 
        // operations and is invoked when an accept operation is complete
        //
        void AcceptEventArg_Completed(object sender, SocketAsyncEventArgs e)
        {


            ProcessAccept(e);


        }

        private void ProcessAccept(SocketAsyncEventArgs e)
        {
            if (!m_nowRestart)
            {
                m_serverState.NewClient();


                // Get the socket for the accepted client connection and put it into the 
                //ReadEventArg object user token
                SocketAsyncEventArgs readEventArgs = m_readWritePool.Pop();
                readEventArgs.UserToken = new AsyncClient();
                ((IAsyncClient)readEventArgs.UserToken).Socket = e.AcceptSocket;
                ((IAsyncClient)readEventArgs.UserToken).ID = m_serverState.SesionCounter;
                ((IAsyncClient)readEventArgs.UserToken).nextTimePing = Environment.TickCount + (m_serverOptions.PingTime);
                ((IAsyncClient)readEventArgs.UserToken).Ping = true;
                ((IAsyncClient)readEventArgs.UserToken).Server = this;
                Clients.Add((IAsyncClient)readEventArgs.UserToken);
                ((IAsyncClient)readEventArgs.UserToken).Reader = new DataReader();
                byte[] initPack = InitClient(m_serverState.SesionCounter);
                ((IAsyncClient)readEventArgs.UserToken).onDataRead += new DataRead(ReadData);



                // Accept the next connection request

                bool willRaiseEvent = ((IAsyncClient)readEventArgs.UserToken).Socket.ReceiveAsync(readEventArgs);
                if (!willRaiseEvent)
                {
                    ProcessReceive(readEventArgs);
                }

                if (((IAsyncClient)readEventArgs.UserToken).Socket != null)
                {
                    SendClient((IAsyncClient)readEventArgs.UserToken, initPack);
                }
                
                m_serverLog.WriteLineLog(TypeLog.GlobalLog, "New client connect: ID='" + ((IAsyncClient)readEventArgs.UserToken).ID + "' IP:Port='" + ((IAsyncClient)readEventArgs.UserToken).Socket.RemoteEndPoint.ToString() + "'");
               // m_serverLog.WriteLineLog(TypeLog.SendLog, "Send client id='" + ((IAsyncClient)readEventArgs.UserToken).ID + "' packet:'Init'");


                StartAccept(e);
            }
        }

       
        
        void SendClient(IAsyncClient client, byte[] data)
        {
            //client.Socket.Send(data);
            SocketAsyncEventArgs eSend = new SocketAsyncEventArgs();
            eSend.UserToken = client;
            eSend.SendPacketsFlags = TransmitFileOptions.ReuseSocket;
            SendPacketsElement el = new SendPacketsElement(data);
            
            eSend.SetBuffer(data, 0, data.Length);
           
            bool willRaiseEventSend = client.Socket.SendAsync(eSend);
            if (!willRaiseEventSend)
            {
                ProcessSend(eSend);
            }
            m_serverLog.WriteLineLog(TypeLog.SendLog, "Send client ID='" + client.ID + "' packet:'"+NetPacket.GetPacket(data[2]).Name+"'");
        }
        void StartRecive(IAsyncClient token, SocketAsyncEventArgs e)
        {
            bool willRaiseEvent = (token).Socket.ReceiveAsync(e);
            if (!willRaiseEvent)
            {
                ProcessReceive(e);
            }
        }
        /// <summary>
        /// Start server
        /// </summary>
        public void Start()
        {
            // create the socket which listens for incoming connections
            m_listenSocket = new Socket(m_serverOptions.IPEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
            m_listenSocket.Bind(m_serverOptions.IPEndPoint);
            // start the server with a listen backlog of 100 connections
            m_listenSocket.Listen(100);

            // post accepts on the listening socket
            StartAccept(null);

            //Стартуем основной цикл обработки Сервера
            //  m_serverOnline = true;

           
            if (m_mainLoop == null)
            {
                m_serverInput.doinput("-?");
                m_mainLoop = new Thread(StartMainLoop);
                m_mainLoop.Start();
                //2 seconds start main loop thread
                System.Threading.Thread.Sleep(2000);
            }
            m_serverLog.WriteLineLog(TypeLog.StateLog, "Server started");
        }
        /// <summary>
        /// Stop Server
        /// </summary>
        public void ShutDown()
        {
            m_serverOnline = false;
            m_serverLog.WriteLineLog(TypeLog.StateLog, "Server ShutDown");
            // m_listenSocket.Shutdown(SocketShutdown.Both);
        }
        /// <summary>
        /// Restart server
        /// </summary>
        /// <param name="time">Time in seconds to restart deffault 0s</param>
        public void Restart(int time = 0)
        {
            m_serverLog.WriteLineLog(TypeLog.StateLog, "Server do Restarted");
            Console.WriteLine("server restarted");
            long curtime = Environment.TickCount;
            //Делаем прогон на время остановки
            while (Environment.TickCount - curtime < time * 1000)
            {
                Thread.Sleep(500);
            }
            //ставим флаг что начинается рестарт
            m_nowRestart = true;
            m_serverOnline = false;
            m_serverState.Online = false;
            m_serverLog.WriteLineLog(TypeLog.StateLog, "Server stop");
            //m_listenSocket.Shutdown(SocketShutdown.Both);
            //  m_listenSocket.Disconnect(true);
            foreach (var item in Clients)
            {
                IAsyncClient token = item as IAsyncClient;

                // close the socket associated with the client
                try
                {
                    token.Socket.Shutdown(SocketShutdown.Send);
                }
                // throws if client process has already closed
                catch (Exception) { }
                token.Socket.Close();
            }

            try
            {
                m_listenSocket.Shutdown(SocketShutdown.Send);
            }
            // throws if client process has already closed
            catch (Exception) { }
            m_listenSocket.Close(2000);
            m_serverLog.WriteLineLog(TypeLog.StateLog, "Server wait 2 seconds");
            Console.WriteLine("do restart wait 2 seconds");
            Clients.Clear();
            Thread.Sleep(2000);
            //инициализуем занаго сервер
            Init();
            // Thread.Sleep(2000);
            // стартуем сервер
            Start();
            m_serverOnline = true;
            m_nowRestart = false;
            Console.WriteLine("Server Restarted");
            m_serverLog.WriteLineLog(TypeLog.StateLog, "Server Ready");
        }

        void doComands()
        {
            string[] tmp = Commands;
            for (int i = 0; i < tmp.Length; i++)
            {
                m_serverInput.doinput(tmp[i]);
            }
        }
        void StartMainLoop()
        {
            m_serverOnline = true;
            m_nowRestart = false;

            while (m_serverOnline)
            {
                if (m_nowRestart)
                {
                    m_serverOnline = false;
                }
                Thread.Sleep(1);
                ///check comands server
                /// 
                doComands();

                CheckPing();
                Update();
                m_serverState.Online = m_serverOnline;
            }
            m_serverOnline = false;
        }
        public virtual void Update()
        {
            if (m_serverOptions.ServerList != null)
            {
                if (m_serverOptions.ServerList.Length > 0)
                {

                    for (int i = 0; i < m_serverOptions.ServerList.Length; i++)
                    {
                        IPAddress ip = IPAddress.Parse(m_serverOptions.ServerList[i].IpServer);
                        if (!_servers[i].Connected)
                        {
                            try
                            {
                                _servers[i].Connect(ip, m_serverOptions.ServerList[i].PortServer);
                                if (_servers[i].Connected)
                                {
                                    m_serverOptions.ServerList[i].Online = true;
                                }
                            }
                            catch (Exception)
                            {


                            }
                        }

                    }
                }
            }
        }
        public virtual void ReadData(IAsyncClient client)
        {

        }
        
        private void CheckPing()
        {
            if (!m_nowRestart && m_serverOptions.PingTime > 0)
            {


                IAsyncClient[] tmp;
                lock (Clients)
                {
                    tmp = Clients.ToArray();
                }

                for (int i = 0; i < tmp.Length; i++)
                {

                    IAsyncClient t = tmp[i];



                    if (t.nextTimePing < Environment.TickCount)
                    {
                        if (t.Ping)
                        {
                            t.Ping = false;
                            SendClient(t, PingClient(t));
                            
                            t.nextTimePing = Environment.TickCount + (m_serverOptions.PingTime);
                        }
                        else
                        {

                            t.needDisconect = true;
                        }
                    }
                    PacketsBilder[] tmp1 = t.GetFromServer();
                    if (tmp1!=null)
                    {
                        for (int pb = 0; pb < tmp1.Length; pb++)
                        {
                            SendClient(t, tmp1[pb].getPacket());
                        }
                    }

                }
            }
        }

        private byte[] PingClient(IAsyncClient kl)
        {
            Engine.NetClasses.NetPing initpack = (NetClasses.NetPing)NetPacket.GetPacket(5);
            initpack.ID = kl.ID;
            initpack.Ping = 0;
            PacketsBilder pb = new PacketsBilder(initpack);
            byte[] temp = pb.getPacket();
            return temp;
        }
        private byte[] InitClient(uint id)
        {
            Engine.NetClasses.Init initpack = (NetClasses.Init)NetPacket.GetPacket(1);
            initpack._id = id;
            initpack._protocol = m_serverOptions.Protocol;

          
            PacketsBilder pb = new PacketsBilder(initpack);


            byte[] temp = pb.getPacket();

            if (pb.checkHash(temp))
            {
                pb = new PacketsBilder(temp, 0);
            }

            

            return temp;
        }

    }
}
